package acme.serviceproviders.webservices;

import java.util.*;

import javax.jws.*;

import org.hibernate.*;

import acme.serviceproviders.*;
import acme.serviceproviders.data.*;
import acme.serviceproviders.data.utility.*;

import javax.jws.WebService;

@WebService
public class CarRentalService {

	private final long CAR_RENTAL_ACCOUNT = 111111111;
	
	@WebMethod
	public Car[] getCars(Date startDate, Date returnDate, String pickUpCity) throws DateIncorrectException 
	{
		if (startDate.after(returnDate))
			throw new DateIncorrectException("Start Date is later then return Date");
		
		if (startDate.before(Calendar.getInstance().getTime()))
			throw new DateIncorrectException("Start Date has already passed");		
		
		Session session = HibernateUtility.getSessionFactory().openSession();
		
		try
		{
		
		List<Car> cars = session.createQuery(
				"select c from Car c where c.city = :city AND c.carId NOT IN(SELECT b.Car.carId FROM CarBooking b WHERE (b.bookedFrom BETWEEN :startDate and :endDate) OR (b.bookedTill BETWEEN :startDate and :endDate) OR ((b.bookedFrom < :startDate) AND (b.bookedTill > :endDate)))")
			.setString("city", pickUpCity)
			.setDate("startDate", startDate)
			.setDate("endDate", returnDate)
			.list();
		
		Car[] result = new Car[cars.size()];
		cars.toArray(result);
		
		return result;
		}
		finally
		{
			session.close();			
		}
	}
	
	@WebMethod
	public String bookCar(int carId, Date startDate, Date returnDate, long accountNumber)
		throws DateIncorrectException, CarNotExistsException, CarBookedException
	{	
		if (startDate.after(returnDate))
			throw new DateIncorrectException("Start Date is later then return Date");		
		
		if (startDate.before(Calendar.getInstance().getTime()))
			throw new DateIncorrectException("Start Date has already passed");		
		
		Session session = HibernateUtility.getSessionFactory().openSession();	
		
		try
		{		
		
		Car car = (Car) 
			session.createQuery("from Car where carId = :carId")
			.setInteger("carId", carId)
			.uniqueResult();
		
		if (car == null)
			throw new CarNotExistsException("Car not found");
		
		if (!isCarAvailable(car.getCarId(), startDate, returnDate, session))
			throw new CarBookedException("Car is already booked during stated period");
		
		float bookingCost = DateHelper.countDaysBetween(startDate, returnDate) * car.getCostsPerDay();
		
		CarBooking booking = new CarBooking();
		booking.setBookingId(UUID.randomUUID().toString());
		booking.setBookedFrom(startDate);
		booking.setBookedTill(returnDate);
		booking.setCar(car); 
		booking.setBookingFee(bookingCost);
		
		chargeBookingCost(accountNumber, bookingCost);
		session.save(booking);
		
		session.flush();
		
		return booking.getBookingId();
		}
		finally
		{
			session.close();
		}
	}

	@WebMethod
	public float cancelCar(String bookingId, long accountNumber) throws IllegalArgumentException
	{	
		Session session = HibernateUtility.getSessionFactory().openSession();		
		
		
		try
		{
		CarBooking booking = (CarBooking) 
			session.createQuery("from CarBooking where bookingId = ?")
			.setString(0, bookingId).uniqueResult();
		
		if (booking == null)
		{
			throw new IllegalArgumentException("Booking not found");
		}
		
		float returnAmount = calculateReturnAmount(booking.getBookedFrom(), booking.getBookingFee());
		session.delete(booking);
		returnBookingCost(accountNumber, returnAmount);
		
		session.flush();
		session.close();
		
		return returnAmount;
		}
		finally
		{
			session.close();
		}
	}
	
	@WebMethod
	public float calculateCancellationFee(String bookingId) throws BookingNotFoundException
	{
		Session session = HibernateUtility.getSessionFactory().openSession();		
		
		try
		{
		CarBooking booking = (CarBooking) 
			session.createQuery("from CarBooking where bookingId = ?")
			.setString(0, bookingId).uniqueResult();		
		
		if (booking == null)
			throw new BookingNotFoundException("Booking not found");
		
		return booking.getBookingFee() - calculateReturnAmount(booking.getBookedFrom(), booking.getBookingFee());
		}
		finally
		{
			session.close();
		}
	}
	
	@WebMethod
	CarBooking getBookingDetails(String bookingId) throws BookingNotFoundException
	{
		Session session = HibernateUtility.getSessionFactory().openSession();

		try {
			CarBooking booking = (CarBooking) session.createQuery(
					"from CarBooking where bookingId = ?").setString(0,
					bookingId).uniqueResult();

			if (booking == null)
				throw new BookingNotFoundException("Booking not found");

			return booking;
		} 
		finally {
			session.close();
		}		
	}	

	private boolean isCarAvailable(int carId, Date startDate, Date returnDate, Session session)
	{
		int count = (Integer) session.createQuery(
			"select count(*) from Car c where c.carId = :carId AND c.carId NOT IN(SELECT b.Car.carId FROM CarBooking b WHERE (b.bookedFrom BETWEEN :startDate and :endDate) OR (b.bookedTill BETWEEN :startDate and :endDate) OR ((b.bookedFrom < :startDate) AND (b.bookedTill > :endDate)))")
			.setInteger("carId", carId)
			.setDate("startDate", startDate)
			.setDate("endDate", returnDate)
			.uniqueResult();
		
		if (count > 0)
			return true;
		
		return false;
	}
	
	private void chargeBookingCost(long accountNumber, float bookingCost)
	{
		// TODO Implement Bank communication logic
		BankWSClient.TransferMoney(accountNumber, CAR_RENTAL_ACCOUNT, bookingCost);
	}
	
	private void returnBookingCost(long accountNumber, float returnAmount)
	{
		// TODO Implement Bank communication logic
		BankWSClient.TransferMoney(CAR_RENTAL_ACCOUNT, accountNumber, returnAmount);		
	}
	
	private float calculateReturnAmount(Date bookingStartDate, float bookingFee) {
		// TODO Apply Cancellation policy
		return bookingFee;
	}	
}